package se.webbzon.oschi01.worldobject.appearance;

import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.TriangleStripArray;
import javax.vecmath.Point3d;
import javax.vecmath.TexCoord2f;

import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;

public class CylinderAppearance3D extends SimpleAppearance3D {

	// The closed geometry model of the cylinder
	private static final Geometry modelClosed = createModelClosed(1,0.5,18,false);
	
	// The open geometry model of the cylinder
	private static final Geometry modelOpen = createModelOpen(1,0.5,18,false);
	
	// The closed back-faced geometry model of the cylinder
	private static final Geometry modelBackfacedClosed = createModelClosed(1,0.5,18,true);
		
	// The open back-faced geometry model of the cylinder
	private static final Geometry modelBackfacedOpen = createModelOpen(1,0.5,18,true);

	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new WorldObjectCylinderAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and height will be equal to 1.
	 * The radius of the cone will be equal to 0.5 and the number
	 * of divisions (sides) will be equal to 18. **/
	public CylinderAppearance3D(boolean open) {
		this(0,0,open,false);
	}
	
	/** Creates a new WorldObjectCylinderAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and height will be equal to 1.
	 * The radius of the cone will be equal to 0.5 and the number
	 * of divisions (sides) will be equal to 18. See WorldObjectSimpleAppearance 
	 * for full description of the attributes renderingOrder and 
	 * simpleAppearanceProperties. The appearance may be back-faced. **/
	public CylinderAppearance3D(int renderingOrder, 
								int simpleAppearanceProperties, 
								boolean open, boolean backfaced) {
		super(	renderingOrder,simpleAppearanceProperties, 
				open ? (backfaced ? modelBackfacedOpen : modelOpen) : 
						(backfaced ? modelBackfacedClosed : modelClosed));
	}
	
	/** Creates a new WorldObjectCylinderAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and height will be equal to 1.
	 * The radius of the cone will be equal to 0.5 and the number
	 * of divisions (sides) is given by the input. **/
	public CylinderAppearance3D(boolean open, int divisions) {
		this(0,0,open,divisions,false);
	}
		
	/** Creates a new WorldObjectCylinderAppearance. The center of the 
	 * base will be placed in (0,-0.5,0) and height will be equal to 1.
	 * The radius of the cone will be equal to 0.5 and the number
	 * of divisions (sides) is given by the input. 
	 * See WorldObjectSimpleAppearance for full description of the
	 * attributes renderingOrder and simpleAppearanceProperties. 
	 * The appearance may be back-faced. **/
	public CylinderAppearance3D(int renderingOrder,
								int simpleAppearanceProperties,
								boolean open, int divisions, boolean backfaced) {
		super(renderingOrder, simpleAppearanceProperties,
				open ? 	createModelOpen(1,0.5,divisions,backfaced) :
						createModelClosed(1,0.5,divisions,backfaced));
	}
		
	/*============================================================
	Private Methods
	============================================================*/
		
	/** Creates the closed geometry model. **/
	private static Geometry createModelClosed(	final double height,
												final double radius,
												final int divisions,
												final boolean backfaced) {
		final double h = height/2, dv = 2*Math.PI/divisions;
		final int[] vertexOrder = backfaced ? new int[] {2,1,4,3} : new int[] {1,2,3,4};
		final int[] stripVertexCount = new int[divisions];
		for (int i = 0; i < divisions; i++)
			stripVertexCount[i] = 6;
		final TriangleStripArray ta = new TriangleStripArray(	6*divisions,
										GeometryArray.COORDINATES +
										GeometryArray.NORMALS + 
										GeometryArray.TEXTURE_COORDINATE_2,
										stripVertexCount);
		
		// Create point and texture coordinate instance to save memory
		final Point3d top = new Point3d(0,h,0);
		final Point3d bottom = new Point3d(0,-h,0);
		final Point3d point = new Point3d();
		final TexCoord2f texCoord = new TexCoord2f();
		
		// Create cylinder base and shell
		double lastCos = 1, lastSin = 0;
		for (int i = 0; i < divisions; i++) {
			final int index = 6*i;
			final double cos = Math.cos((i+1)*dv), sin = Math.sin((i+1)*dv);
			
			texCoord.set(1-(i+0.5f)/divisions, 1);
			ta.setTextureCoordinate(0, index, texCoord);
			ta.setCoordinate(index, bottom);
			
			texCoord.set(1-(i+1)/(float) divisions, 0);
			ta.setTextureCoordinate(0, index+vertexOrder[0], texCoord);
			point.set(radius*cos,-h,-radius*sin);
			ta.setCoordinate(index+vertexOrder[0], point);
			
			texCoord.set(1-i/(float) divisions, 0);
			ta.setTextureCoordinate(0, index+vertexOrder[1], texCoord);
			point.set(radius*lastCos,-h,-radius*lastSin);
			ta.setCoordinate(index+vertexOrder[1], point);
			
			texCoord.set(1-(i+1)/(float) divisions, 1);
			ta.setTextureCoordinate(0, index+vertexOrder[2], texCoord);
			point.set(radius*cos,h,-radius*sin);
			ta.setCoordinate(index+vertexOrder[2], point);
			
			texCoord.set(1-i/(float) divisions, 1);
			ta.setTextureCoordinate(0, index+vertexOrder[3], texCoord);
			point.set(radius*lastCos,h,-radius*lastSin);
			ta.setCoordinate(index+vertexOrder[3], point);
			
			texCoord.set(1-(i+0.5f)/divisions, 0);
			ta.setTextureCoordinate(0, index+5, texCoord);
			ta.setCoordinate(index+5, top);
			
			lastCos = cos;
			lastSin = sin;
		}
		
		// Generate normals
		final GeometryInfo g = new GeometryInfo(ta);
		final NormalGenerator ng = new NormalGenerator();
		ng.generateNormals(g);
		
		return g.getGeometryArray();
	}
	
	/** Creates the open geometry model. **/
	private static Geometry createModelOpen(final double height,
											final double radius,
											final int divisions,
											final boolean backfaced) {
		final double h = height/2, dv = 2*Math.PI/divisions;
		final int[] vertexOrder = backfaced ? new int[] {1,0} : new int[] {0,1};
		final TriangleStripArray ta = new TriangleStripArray(2*divisions+2,
												GeometryArray.COORDINATES +
												GeometryArray.NORMALS + 
												GeometryArray.TEXTURE_COORDINATE_2,
												new int[] {2*divisions+2});
		
		// Create point and texture coordinate instance to save memory
		final Point3d point = new Point3d();
		final TexCoord2f texCoord = new TexCoord2f();
		
		// Create cylinder shell
		final float divs = (float) divisions;
		for (int i = 0; i <= divisions; i++) {
			final int index = 2*i;
			final double v = i*dv;
			final float textureX = i/divs;
			final double cordX = radius*Math.cos(v), cordZ = radius*Math.sin(v);

			texCoord.set(textureX, 0);
			ta.setTextureCoordinate(0, index+vertexOrder[0], texCoord);
			point.set(cordX,-h,cordZ);
			ta.setCoordinate(index+vertexOrder[0], point);
			
			texCoord.setY(1);
			ta.setTextureCoordinate(0, index+vertexOrder[1], texCoord);
			point.setY(h);
			ta.setCoordinate(index+vertexOrder[1], point);
		}
		
		// Generate normals
		final GeometryInfo g = new GeometryInfo(ta);
		final NormalGenerator ng = new NormalGenerator();
		ng.generateNormals(g);
		
		return g.getGeometryArray();
	}
	
}
